package com.example.apt_compile

import com.example.apt_annotation.ViewBind
import com.google.auto.service.AutoService
import com.squareup.javapoet.*
import java.io.IOException
import java.io.Writer
import java.util.HashMap
import java.util.HashSet
import java.util.LinkedHashSet
import javax.annotation.processing.*
import javax.lang.model.SourceVersion
import javax.lang.model.element.*
import javax.lang.model.util.Elements
import javax.tools.Diagnostic
import javax.tools.JavaFileObject

/**
 * @author tgw
 * @date 2021/9/30
 * @describe 多个个activity中的拼接的
 *
 *
 * 参考：https://www.jianshu.com/p/40c62ebf53e0
 * 存在坑 多个注解的时候  应该用for循环拼接findviewbyid的操作，以绑定多个
 *
 *
 * //@SupportedAnnotationTypes("com.example.compiler.BindView")  //该注解与 下面的getSupportedAnnotationTypes（）方法一个含义

 */
//AutoService 注解是帮助我们用来自动生成 与java同等级目录下的
// E:\MyLearing\MyAptUseLearing\apt-compile\src\main\resources\META-INF\services\javax.annotation.processing.Processor 文件的
//里面的路径为自定义注解的 ViewBindProcessor 全路径eg：com.example.apt_compile.ViewBindProcessor
//注意：不能使用该注解的同时，又自己创建这个META-IN下的F文件，否则报错
//注意：当process没有调用的时候排查一下语言，Java使用annotationProcessor，kotlin语言使用kapt替代annotationProcessor
//@AutoService(Processor::class)
class ViewBindProcessorTow : AbstractProcessor() {
    //用来创建新源、类或辅助文件的 Filer。
    private lateinit var mFiler: Filer
    private var mMessager: Messager? = null

    private var mElementUtils: Elements? = null

    //用于在不同包路径下的类使用注解时，存储值，方便后续代码的拼接
    private val mActivityMap: HashMap<String, ViewBindParamBean> =
        HashMap<String, ViewBindParamBean>()

    @Synchronized
    override fun init(processingEnvironment: ProcessingEnvironment) {
        super.init(processingEnvironment)

        /**
         *  用来创建新源、类或辅助文件的 Filer。
         */
        mFiler = processingEnvironment.filer
        /**
         * 返回用来报告错误、警报和其他通知的 Messager。
         */
        mMessager = processingEnvironment.messager
        /**
         * 返回用来在元素上进行操作的某些实用工具方法的实现。<br>
         *
         * Elements是一个工具类，可以处理相关Element（包括ExecutableElement, PackageElement, TypeElement, TypeParameterElement, VariableElement）
         */
        mElementUtils = processingEnvironment.elementUtils
        print("注解处理init")
    }

    /**
     * 指定使用的Java版本，通常这里返回SourceVersion.latestSupported()
     */
    override fun getSupportedSourceVersion(): SourceVersion {
        return SourceVersion.latestSupported()
    }

    /**
     * 指定这个注解处理器是注册给哪个注解的，这里说明是注解BindView
     */
    override fun getSupportedAnnotationTypes(): Set<String> {
        val supportTypes: HashSet<String> = LinkedHashSet()
        print("注解处理getSupportedAnnotationTypes")
        supportTypes.add(ViewBind::class.java.canonicalName)
        return supportTypes
    }

    override fun process(set: Set<TypeElement>, roundEnvironment: RoundEnvironment): Boolean {
        mActivityMap.clear()
        print("注解处理process")
        if (set != null && set.isNotEmpty()) {
            //通过getElementsAnnotatedWith得到所有注解elements
            val bindViewElements: Set<Element> = roundEnvironment.getElementsAnnotatedWith(
                ViewBind::class.java
            )
            var packName: String? = null
            var className: String? = null
            for (element in bindViewElements) {
                //1.获取包名
                val packageElement: PackageElement = mElementUtils!!.getPackageOf(element)
                packName = packageElement.qualifiedName.toString()
                print(String.format("package = %s", packName))
                print("注解数量" + bindViewElements.size)


                //2.注解所在的类的类名
                val enclosingElement: TypeElement = element.enclosingElement as TypeElement
                className = enclosingElement.simpleName.toString()
                print(String.format("类名-类包括包名的完整路径 enclosindClass = %s", enclosingElement))
                if (!mActivityMap.containsKey(enclosingElement.toString())) {
                    val bean = ViewBindParamBean()
                    bean.packageName = packName
                    bean.className = className
                    mActivityMap[enclosingElement.toString()] = bean
                    print("类的类名$enclosingElement")
                }


                //因为BindView只作用于filed，所以这里可直接进行强转
                val bindViewElement: VariableElement = element as VariableElement
                //3.获取注解的成员变量名
                val fieldName: String = bindViewElement.simpleName.toString()
                print(String.format("成员变量名 enclosindClass = %s", fieldName))
                //4.获取注解元数据
                val bindView: ViewBind = element.getAnnotation<ViewBind>(ViewBind::class.java)
                val id: Int = bindView.value
                print(String.format("%s = %d", fieldName, id))
            }

            //1. 开始拼接代码 /**字符串普通方式拼接*/
//            creatMethod(bindViewElements)

            //2.javapoet框架拼接
            creatMethodPoet(bindViewElements);
            return true
        }
        return false
    }

    /**
     * 1.字符串普通方式拼接
     */
    private fun creatMethod(bindViewElements: Set<Element>) {
        var packName: String?
        var className: String?
        for ((classKey, viewBindParamBean) in mActivityMap) {
            //类的完整名称
            packName = viewBindParamBean.packageName
            className = viewBindParamBean.className
            print("遍历map注解的类包名称与类名称：className=" + className + "packName=" + packName)
            print("遍历map当前包下面有几个加了注解的类：" + mActivityMap.size)
            try {
                val newClassName = "$className$\$BindAdapterImpTow"
                val jfo: JavaFileObject =
                    mFiler.createSourceFile("$packName.$newClassName", *arrayOf<Element>())
                val writer: Writer = jfo.openWriter()
                writer.write("package $packName;")
                writer.write("\n\n")
                writer.write("import com.example.apt_api.BindAdapter;")
                //                writer.write("\n\n");
//                writer.write("import "+classKey+";");
                writer.write("\n\n\n")
                writer.write("public class $newClassName implements BindAdapter<$className> {")
                writer.write("\n\n")
                writer.write("public void bind($className target) {")
                for (element in bindViewElements) { //这里是所有的 使用了注解的元素未区分 不同包与类
                    //1.因为BindView只作用于filed，所以这里可直接进行强转
                    val bindViewElement: VariableElement = element as VariableElement
                    //2.注解所在的类的类名
                    val enclosingElement: TypeElement = element.enclosingElement as TypeElement
                    //3.匹配对应类下面的元素
                    if (classKey == enclosingElement.toString()) {
                        //3.获取注解的成员变量名
                        val fieldName: String = bindViewElement.getSimpleName().toString()

                        //4.获取注解元数据
                        val bindView: ViewBind =
                            element.getAnnotation<ViewBind>(ViewBind::class.java)
                        val id: Int = bindView.value
                        print("有多个view绑定")
                        //绑定注解
                        writer.write("\n\n")
                        writer.write("target.$fieldName = target.findViewById($id);")
                    }
                }
                writer.write("\n")
                writer.write("  }")
                writer.write("\n\n")
                writer.write("}")
                writer.flush()
                writer.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }

    /**
     * javapoet 用法  参考：https://blog.csdn.net/qq_26376637/article/details/52374063
     */
    private fun creatMethodPoet(bindViewElements: Set<Element>) {
        var packName: String?
        var className: String?
        for ((classKey, viewBindParamBean) in mActivityMap) {
            //类的完整名称
            packName = viewBindParamBean.packageName
            className = viewBindParamBean.className
            print("遍历map：className=" + className + "packName=" + packName)
            print("遍历map：" + mActivityMap.size)
            val newClassName = "$className$\$BindAdapterImpTow"
            //获取继承的接口
            val iBindAdapter: ClassName = ClassName.get("com.example.apt_api", "BindAdapter")
            //获取继承的接口中的参数
            val iBindAdapterParam: ClassName = ClassName.bestGuess(className)

            //拼接方法中的内容
            var content = ""
            for (element in bindViewElements) { //这里是所有的 使用了注解的元素未区分 不同包与类
                //1.因为BindView只作用于filed，所以这里可直接进行强转
                val bindViewElement: VariableElement = element as VariableElement
                //2.注解所在的类的类名
                val enclosingElement: TypeElement = element.enclosingElement as TypeElement
                //3.匹配对应类下面的元素
                if (classKey == enclosingElement.toString()) {
                    //3.获取注解的成员变量名
                    val fieldName: String = bindViewElement.simpleName.toString()
                    //4.获取注解元数据
                    val bindView: ViewBind = element.getAnnotation<ViewBind>(ViewBind::class.java)
                    val id: Int = bindView.value
                    content = "${content}target.$fieldName = target.findViewById($id);"
                }
            }
            val main: MethodSpec = MethodSpec.methodBuilder("bind")
                .addModifiers(Modifier.PUBLIC)
                .returns(Void.TYPE)
                .addParameter(iBindAdapterParam, "target")
                .addStatement(content)
                .build()
            val bind: TypeSpec = TypeSpec.classBuilder(newClassName)
                .addModifiers(Modifier.PUBLIC)
                .addMethod(main)
                .addSuperinterface(
                    ParameterizedTypeName.get(
                        iBindAdapter,
                        iBindAdapterParam
                    )
                ) //添加继承
                .build()
            val javaFile: JavaFile = JavaFile.builder(packName, bind).build()
            try {
                //　生成文件
                javaFile.writeTo(processingEnv.filer)
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }

    /**
     * 打印编译期间的日志
     */
    private fun print(msg: String) {
        mMessager?.printMessage(Diagnostic.Kind.NOTE, "注解日志：$msg")
    }
}